/*
 * jxUtils Project (2014)
 * 
 * by Denilson Edinaldo Pinto
 * 
 * LGPL
 * Copyright (C) 2014
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.jxUtils.file;

import java.awt.Desktop;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Date;

import javax.swing.ImageIcon;
import javax.swing.filechooser.FileSystemView;

import org.jxUtils.check.ParameterCheck;
import org.jxUtils.date.DateUtil;
import org.jxUtils.file.type.AudioType;
import org.jxUtils.file.type.CompressType;
import org.jxUtils.file.type.DocumentType;
import org.jxUtils.file.type.DrawingType;
import org.jxUtils.file.type.ExecutableType;
import org.jxUtils.file.type.ImageType;
import org.jxUtils.file.type.PresentationType;
import org.jxUtils.file.type.SimpleTextType;
import org.jxUtils.file.type.SpreadsheetType;
import org.jxUtils.file.type.VideoType;
import org.jxUtils.i18n.I18N;
import org.jxUtils.image.ImageUtil;
import org.jxUtils.system.OperatingSystem;

import sun.awt.shell.ShellFolder;

/**
 * @author Denilson Edinaldo Pinto
 * @porject jxUtils
 * @package org.jxUtils.file
 * @date 31/05/2014 - 09:15:36
 */
public class JXFile extends File {
	
	private static final long serialVersionUID = 2350775561852490685L;
	
	private static final String MIME_ICON_FULL_NAME = "/org/jxUtils/resources/icon/mimetype/:NAME-:SIZE.png";
	
	private static final String PLACE_ICON_FULL_NAME = "/org/jxUtils/resources/icon/place/:NAME-:SIZE.png";
	
	private FileSize size;
	
	private byte[] byteArray = null;
	
	/**
	 * @param file
	 */
	public JXFile(final File file) {
	
		this(file.getAbsolutePath());
	}
	
	/**
	 * @param parent
	 * @param child
	 */
	public JXFile(final File parent, final String child) {
	
		super(parent, child);
		
		init();
	}
	
	/**
	 * @param pathname
	 */
	public JXFile(final String pathname) {
	
		super(pathname);
		
		init();
	}
	
	/**
	 * @param parent
	 * @param child
	 */
	public JXFile(final String parent, final String child) {
	
		super(parent, child);
		
		init();
	}
	
	/**
	 * @param uri
	 */
	public JXFile(final URI uri) {
	
		super(uri);
		
		init();
	}
	
	/**
	 * @param file
	 * @param directory
	 * @throws IOException
	 */
	public static final void copy(File file, File directory) throws IOException {
	
		ParameterCheck.isNull(file, "file");
		ParameterCheck.isNull(directory, "directory");
		
		//
		if( !file.isFile()) {
			
			throw new IOException(I18N.file().pathIsNotFile(file.toString()));
		}
		
		//
		if( !directory.isDirectory()) {
			
			throw new IOException(I18N.file().pathIsNotDirectory(directory.toString()));
		}
		
		File copy = new File(directory, file.getName());
		
		Path sourcePath = FileSystems.getDefault().getPath(file.getPath());
		Path copyPath = FileSystems.getDefault().getPath(copy.getPath());
		
		Files.copy(sourcePath, copyPath);
	}
	
	/**
	 * @param file
	 * @throws IOException
	 */
	public static final void edit(final File file) throws IOException {
	
		ParameterCheck.isNull(file, "file");
		
		//
		if(Desktop.isDesktopSupported()) {
			
			Desktop.getDesktop().edit(file);
		}
	}
	
	/**
	 * @param file
	 * @return The filename without extension
	 * @since if file name = "myFile.txt", return "myFile"
	 */
	public static final String getBaseName(final File file) {
	
		ParameterCheck.isNull(file, "file");
		
		return file.getName().substring(0, file.getName().indexOf(getFullExtension(file)));
	}
	
	/**
	 * @param file
	 * @return extension
	 * @since if file name = "myFile.txt", return "txt" <br>
	 *        if file name = "myFile", return empty String <br>
	 *        if file is directory, return empty String
	 */
	public static final String getExtension(final File file) {
	
		ParameterCheck.isNull(file, "file");
		
		//
		if(file.isDirectory()) {
			
			return "";
		}
		
		String extension = "";
		final String fileName = file.getName();
		
		final int i = fileName.lastIndexOf('.');
		
		//
		if(i > 0 && i < fileName.length() - 1) {
			
			extension = fileName.substring(i + 1).toLowerCase();
		}
		
		return extension;
	}
	
	/**
	 * @param file
	 * @return extension
	 * @since if file name = "myFile.txt", return ".txt" <br>
	 *        if file name = "myFile", return empty String <br>
	 *        if file is directory, return empty String
	 */
	public static final String getFullExtension(final File file) {
	
		return "." + getExtension(file);
	}
	
	/**
	 * @param file
	 * @param size
	 *        16 or 32 pixel
	 * @return file System Icon
	 * @throws FileNotFoundException
	 */
	public static ImageIcon getIcon(final File file, int size) throws FileNotFoundException {
	
		ParameterCheck.isNull(file, "file");
		
		ImageIcon icon = null;
		
		//
		if(file.isFile()) {
			
			String extension = getFullExtension(file);
			String name = "file";
			
			//
			if(AudioType.containsInAny(extension)) {
				
				name = "audio";
				
			}else if(CompressType.containsInAny(extension)) {
				
				name = "package";
				
			}else if(DocumentType.containsInAny(extension)) {
				
				name = "document";
				
			}else if(DrawingType.containsInAny(extension)) {
				
				name = "drawing";
				
			}else if(ExecutableType.containsInAny(extension)) {
				
				name = "executable";
				
			}else if(ImageType.containsInAny(extension)) {
				
				name = "image";
				
			}else if(PresentationType.containsInAny(extension)) {
				
				name = "presentation";
				
			}else if(SimpleTextType.containsInAny(extension)) {
				
				name = "text";
				
			}else if(SpreadsheetType.containsInAny(extension)) {
				
				name = "spreadsheet";
				
			}else if(VideoType.containsInAny(extension)) {
				
				name = "video";
			}
			
			icon = new ImageIcon(JXFile.class.getResource(MIME_ICON_FULL_NAME.replace(":NAME", name).replace(":SIZE", size + "x" + size)));
			
		}else if(file.isDirectory()) {
			
			icon = new ImageIcon(JXFile.class.getResource(PLACE_ICON_FULL_NAME.replace(":NAME", "folder").replace(":SIZE", size + "x" + size)));
			
		}else {
			
			icon = ImageUtil.toImageIcon(FileSystemView.getFileSystemView().getSystemIcon(file));
		}
		
		return icon;
	}
	
	/**
	 * @param file
	 * @return file System Icon
	 * @throws FileNotFoundException
	 */
	public static ImageIcon getIcon(final File file) throws FileNotFoundException {
	
		return getIcon(file, 32);
	}
	
	/**
	 * @param file
	 * @return file Type
	 * @throws FileNotFoundException
	 */
	public final static String getType(File file) throws FileNotFoundException {
	
		ParameterCheck.isNull(file, "file");
		
		ShellFolder sf = ShellFolder.getShellFolder(file);
		
		return sf.getFolderType();
	}
	
	/**
	 * On UNIX systems, a file is considered to be hidden if its name begins with a period character ('.').<br>
	 * On Microsoft Windows systems, a file is considered to be hidden if it has been marked as such in the filesystem.
	 * 
	 * @param file
	 * @param hidden
	 * @throws IOException
	 */
	public static final void hidden(final File file, final boolean hidden) throws IOException {
	
		ParameterCheck.isNull(file, "file");
		
		//
		if(OperatingSystem.isPosix() || OperatingSystem.isMac()) {
			
			//
			if(hidden) {
				
				file.renameTo(new File(file.getParent(), "." + file.getName()));
				
			}else if(file.isHidden()) {
				
				file.renameTo(new File(file.getParent(), file.getName().substring(1)));
			}
			
		}else if(OperatingSystem.isWindows()) {
			
			Path path = FileSystems.getDefault().getPath(file.getParent(), file.getName());
			
			path = Files.setAttribute(path, "dos:hidden", hidden);
		}
	}
	
	/**
	 * @param file
	 * @throws IOException
	 */
	public static final void open(final File file) throws IOException {
	
		ParameterCheck.isNull(file, "file");
		
		//
		if(Desktop.isDesktopSupported()) {
			
			Desktop.getDesktop().open(file);
		}
	}
	
	/**
	 * @param file
	 * @return object
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	@SuppressWarnings("unchecked")
	public static final <T> T readObject(final File file) throws IOException, ClassNotFoundException {
	
		ParameterCheck.isNull(file, "file");
		
		T object = null;
		
		//
		try (ObjectInputStream stream = new ObjectInputStream(new FileInputStream(file))) {
			
			object = (T)stream.readObject();
			
		}catch(IOException | ClassNotFoundException exception) {
			
			throw exception;
		}
		
		return object;
	}
	
	/**
	 * @param file
	 * @return Reads the contents of a file into a byte array.
	 * @throws URISyntaxException
	 * @throws IOException
	 */
	public static final byte[] toByteArray(File file) throws IOException, URISyntaxException {
	
		return Files.readAllBytes(Paths.get(file.toURI()));
	}
	
	/**
	 * @param file
	 * @return {@link Path}
	 */
	public static final Path toPath(final File file) {
	
		ParameterCheck.isNull(file, "file");
		
		return Paths.get(file.toURI());
	}
	
	/**
	 * @param object
	 * @param file
	 * @throws IOException
	 */
	public static final void writeObject(final Object object, final File file) throws IOException {
	
		writeObject(object, file, false);
	}
	
	/**
	 * @param object
	 * @param file
	 * @param hidden
	 * @throws IOException
	 */
	public static final void writeObject(final Object object, final File file, final boolean hidden) throws IOException {
	
		ParameterCheck.isNull(object, "object");
		ParameterCheck.isNull(file, "file");
		
		//
		try (ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream(file))) {
			
			stream.writeObject(object);
			
		}catch(final IOException ioException) {
			
			throw ioException;
		}
		
		//
		JXFile.hidden(file, hidden);
	}
	
	/**
	 * @param file
	 * @param text
	 * @param options
	 * @throws IOException
	 */
	public static final void writeString(final File file, final String text, final OpenOption... options) throws IOException {
	
		ParameterCheck.isNull(text, "text");
		
		Files.write(toPath(file), text.getBytes(), options);
	}
	
	/**
	 * 
	 */
	private void init() {
	
		size = new FileSize(length());
	}
	
	/**
	 * @throws IOException
	 */
	public final void edit() throws IOException {
	
		edit(this);
	}
	
	/**
	 * @return The filename without extension
	 * @since if file name = "myFile.txt", return "myFile"
	 */
	public final String getBaseName() {
	
		return getBaseName(this);
	}
	
	/**
	 * @return the byteArray or null
	 * @see #loadByteArray()
	 */
	public final byte[] getByteArray() {
	
		return byteArray;
	}
	
	/**
	 * @return a human-readable display last modified
	 */
	public final String getDisplayLastModified() {
	
		return DateUtil.parse(new Date(lastModified()));
	}
	
	/**
	 * @return a human-readable display size
	 */
	public final String getDisplaySize() {
	
		return size.toString();
	}
	
	/**
	 * @param file
	 * @return extension
	 * @since if file name = "myFile.txt", return "txt" <br>
	 *        if file name = "myFile", return empty String <br>
	 *        if file is directory, return empty String
	 */
	public final String getExtension() {
	
		return getExtension(this);
	}
	
	/**
	 * @return extension
	 * @since if file name = "myFile.txt", return "txt" <br>
	 *        if file name = "myFile", return empty String <br>
	 *        if file is directory, return empty String
	 */
	public final String getFullExtension() {
	
		return getFullExtension(this);
	}
	
	/**
	 * @return file Icon
	 * @throws FileNotFoundException
	 */
	public final ImageIcon getIcon() throws FileNotFoundException {
	
		return getIcon(this);
	}
	
	/**
	 * @return A {@link Date} value representing the time the file was last modified
	 */
	public final Date getLastModified() {
	
		return new Date(lastModified());
	}
	
	/**
	 * @return {@link FileSize}
	 */
	public final FileSize getSize() {
	
		return size;
	}
	
	/**
	 * @return file Type
	 * @throws FileNotFoundException
	 */
	public final String getType() throws FileNotFoundException {
	
		return getType(this);
	}
	
	/**
	 * On UNIX systems, a file is considered to be hidden if its name begins with a period character ('.').<br>
	 * On Microsoft Windows systems, a file is considered to be hidden if it has been marked as such in the filesystem.
	 * 
	 * @param hidden
	 * @throws IOException
	 */
	public final void hidden(final boolean hidden) throws IOException {
	
		hidden(this, hidden);
	}
	
	/**
	 * @return Reads the contents of this file into a byte array.
	 * @throws URISyntaxException
	 * @throws IOException
	 */
	public final byte[] loadByteArray() throws IOException, URISyntaxException {
	
		byteArray = toByteArray(this);
		
		return byteArray;
	}
	
	/**
	 * @param newDirectory
	 * @throws IOException
	 */
	public final boolean moveTo(final File newDirectory) throws IOException {
	
		ParameterCheck.isNull(newDirectory, "newDirectory");
		
		//
		if( !newDirectory.isDirectory()) {
			
			throw new IOException(I18N.file().pathIsNotDirectory(newDirectory.getAbsolutePath()));
		}
		
		//
		if( !newDirectory.exists()) {
			
			throw new IOException(I18N.file().directoryNotExists(newDirectory.getAbsolutePath()));
		}
		
		return renameTo(new JXFile(newDirectory, getName()));
	}
	
	/**
	 * @throws IOException
	 */
	public final void open() throws IOException {
	
		open(this);
	}
	
	/**
	 * @return object
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	public final <T> T readObject() throws IOException, ClassNotFoundException {
	
		return readObject(this);
	}
	
	/**
	 * @param newFileName
	 * @return <code>true</code> if and only if the renaming succeeded; <code>false</code> otherwise
	 */
	public final boolean renameTo(final String newFileName) {
	
		ParameterCheck.isNullOrAbsoluteEmpty(newFileName, "newFileName");
		
		return renameTo(new File(getParentFile(), newFileName + getFullExtension()));
	}
	
	/**
	 * @return Reads the contents of this file into a byte array.
	 * @throws URISyntaxException
	 * @throws IOException
	 */
	public final byte[] toByteArray() throws IOException, URISyntaxException {
	
		return toByteArray(this);
	}
	
	/**
	 * @return the {@link Path}
	 */
	@Override
	public Path toPath() {
	
		return Paths.get(toURI());
	}
	
	/**
	 * @param object
	 * @throws IOException
	 */
	public final void writeObject(final Object object) throws IOException {
	
		writeObject(object, this);
	}
	
	/**
	 * @param object
	 * @param hidden
	 * @throws IOException
	 */
	public final void writeObject(final Object object, final boolean hidden) throws IOException {
	
		writeObject(object, this, hidden);
	}
	
	/**
	 * @param text
	 * @param options
	 * @throws IOException
	 */
	public final void writeString(final String text, final OpenOption... options) throws IOException {
	
		ParameterCheck.isNull(text, "text");
		
		Files.write(toPath(), text.getBytes(), options);
	}
}
